package com.derbysoft.lowcostadapter.dswitch3;

import com.derbysoft.dswitch.dto.hotel.avail.HotelAvailRQ;
import com.derbysoft.dswitch.dto.hotel.common.TransactionActionType;
import com.derbysoft.dswitch.dto.hotel.reservation.ReservationRoomStayDTO;
import com.derbysoft.dswitch.remote.hotel.dto.*;
import com.derbysoft.lowcostadapter.commons.exception.ChannelNotFoundException;
import com.derbysoft.lowcostadapter.commons.log.LogBody;
import com.derbysoft.lowcostadapter.commons.log.LogUtils;
import com.derbysoft.lowcostadapter.commons.log.PerformanceLogger;
import com.derbysoft.lowcostadapter.commons.log.TransactionLogger;
import com.derbysoft.lowcostadapter.commons.model.AvailabilityProvider;
import com.derbysoft.lowcostadapter.commons.model.Channel;
import com.derbysoft.lowcostadapter.commons.model.OperationType;
import com.derbysoft.lowcostadapter.commons.redis.service.ChannelCacheService;
import com.derbysoft.lowcostadapter.commons.redis.service.SystemVariableCacheService;
import com.derbysoft.lowcostadapter.commons.repository.ChannelRepository;
import com.derbysoft.lowcostadapter.commons.repository.ReservationLogRepository;
import com.derbysoft.lowcostadapter.commons.repository.SystemVariableRepository;
import com.derbysoft.lowcostadapter.commons.repository.TransactionLogRepository;
import com.derbysoft.lowcostadapter.dswitch3.handler.RemoteServiceHandler;
import com.derbysoft.lowcostadapter.dswitch3.service.impl.BaseHotelRemoteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.Executor;

/**
 * Created by: jason
 * Date: 2012-05-15
 */
@Service("ds3hotelProviderWebService")
public class HotelRemoteServiceProxy extends BaseHotelRemoteService {

    @Autowired
    private ChannelCacheService channelCacheService;

    @Autowired
    private ChannelRepository channelRepository;

    @Override
    public HotelAvailResponse getAvailability(HotelAvailRequest request) {
        PerformanceLogger.init();
        List<String> hotelCodes = request.getHotelAvailRQ().getAvailCriteria().getHotelCodesList();
        if (hotelCodes.size() > 1) {
            return unsupportedOperationResponse(request);
        }
        HotelAvailResponse response;
        Channel channel = loadChannel(request.getHeader().getSource());
        if (channel.getAvailabilityProvider() == AvailabilityProvider.FROM_LOCAL_CACHE) {
            response = hotelAvailHandler.handle(request);
        } else {
            response = storageAvailHandler.handle(request);
        }

        saveAvailPerformanceLog(request, response);

        saveSessionLog();

        return response;
    }

    @Override
    public HotelAvailResponse getNoCachedAvailability(HotelAvailRequest request) {
        PerformanceLogger.init();
        List<String> hotelCodes = request.getHotelAvailRQ().getAvailCriteria().getHotelCodesList();
        if (hotelCodes.size() > 1) {
            return unsupportedOperationResponse(request);
        }

        HotelAvailResponse response = hotelNoCachedAvailHandler.handle(request);

        saveAvailPerformanceLog(request, response);

        saveSessionLog();

        return response;
    }

    @Override
    public HotelReservationResponse makeReservation(HotelReservationRequest request) {
        HotelReservationResponse response;
        PerformanceLogger.init();
        if (request.getHotelReservationRQ().getResActionType() == TransactionActionType.BOOK) {
            response = hotelReservationHandler.handle(request);
        } else {
            response = new HotelReservationResponse();
            response.setTaskId(request.getHeader().getTaskId());
            ErrorDTO error = new ErrorDTO();
            error.setCode("System");
            error.setSource(adapter);
            String message = String.format("Unsupported operation [%s]", request.getHotelReservationRQ().getResActionType());
            error.setMessage(message);
            response.setError(error);
        }

        saveReservationPerformanceLog(request, response);

        saveSessionLog();

        return response;
    }


    @Override
    public HotelCancelResponse cancelReservation(HotelCancelRequest request) {
        HotelCancelResponse response = hotelCancelHandler.handle(request);

        saveCancellationPerformanceLog(request, response);

        saveSessionLog();

        return response;
    }


    private Channel loadChannel(String channelCode) {
        Channel channel = channelCacheService.get(channelCode);
        if (channel == null) {
            channel = channelRepository.load(channelCode);
        }
        if (channel == null) {
            throw new ChannelNotFoundException(channelCode);
        }
        return channel;
    }

    private void saveSessionLog() {
        LogUtils.saveLogs(executor, systemVariableCacheService,
                systemVariableRepository, transactionLogRepository, reservationLogRepository);
    }

    private void saveCancellationPerformanceLog(HotelCancelRequest request, HotelCancelResponse response) {
        PerformanceLogger.init();
        PerformanceLogger.append(LogBody.REQUEST_TYPE, OperationType.CANCELLATION.name());
        setPerformanceLogHeader(request);
        if (response.getError() != null) {
            TransactionLogger.getReservationLogger().get().setError(true);
            TransactionLogger.getReservationLogger().get().setException(response.getError().getMessage());
            PerformanceLogger.append(LogBody.PROCESSED_RESULT, LogBody.ProcessResult.Fail.name());
        } else {
            PerformanceLogger.append(LogBody.PROCESSED_RESULT, LogBody.ProcessResult.Success.name());
        }
        PerformanceLogger.save(executor);
    }

    private void saveReservationPerformanceLog(HotelReservationRequest request, HotelReservationResponse response) {
        ReservationRoomStayDTO roomStayDTO = request.getHotelReservationRQ().getHotelReservation().getRoomStaysList().get(0);
        setPerformanceLogHeader(request);
        if (request.getHotelReservationRQ().getResActionType() == TransactionActionType.BOOK) {
            PerformanceLogger.append(LogBody.REQUEST_TYPE, OperationType.HOTEL_RESERVATION.name());
        } else {
            PerformanceLogger.append(LogBody.REQUEST_TYPE, OperationType.UNSUPPORTED_OPERATION.name());
        }

        PerformanceLogger.append(LogBody.DERBY_HOTEL_CODES, request.getHotelReservationRQ().getHotelReservation().getHotelCode());
        PerformanceLogger.append(LogBody.CHECK_IN, roomStayDTO.getStayDateRange().getCheckin());
        PerformanceLogger.append(LogBody.CHECK_OUT, roomStayDTO.getStayDateRange().getCheckout());
        PerformanceLogger.append(LogBody.RATE_PLAN, roomStayDTO.getRatePlanCode());
        PerformanceLogger.append(LogBody.ROOM_TYPE, roomStayDTO.getRoomTypeCode());
        PerformanceLogger.append(LogBody.NUMBER_OF_UNIT, String.valueOf(roomStayDTO.getNumberOfUnits()));
        PerformanceLogger.append(LogBody.ADULT, String.valueOf(roomStayDTO.getAdultCount()));
        PerformanceLogger.append(LogBody.CHILD, String.valueOf(roomStayDTO.getChildCount()));
        PerformanceLogger.append(LogBody.ERSP_NUMBER, request.getHotelReservationRQ().getErsp());

        if (response.getError() != null) {
            TransactionLogger.getReservationLogger().get().setError(true);
            TransactionLogger.getReservationLogger().get().setException(response.getError().getMessage());
            PerformanceLogger.append(LogBody.PROCESSED_RESULT, LogBody.ProcessResult.Fail.name());
        } else {
            PerformanceLogger.append(LogBody.PROCESSED_RESULT, LogBody.ProcessResult.Success.name());
        }
        PerformanceLogger.save(executor);
    }

    private HotelAvailResponse unsupportedOperationResponse(HotelAvailRequest request) {
        HotelAvailResponse hotelAvailResponse = new HotelAvailResponse();
        hotelAvailResponse.setTaskId(request.getHeader().getTaskId());
        ErrorDTO error = new ErrorDTO();
        error.setCode("System");
        error.setSource(adapter);
        String message = String.format("Unsupported operation [GetMultiAvailability]");
        error.setMessage(message);
        hotelAvailResponse.setError(error);
        return hotelAvailResponse;
    }

    private void saveAvailPerformanceLog(HotelAvailRequest request, HotelAvailResponse response) {
        PerformanceLogger.append(LogBody.REQUEST_TYPE, OperationType.HOTEL_AVAIL.name());
        setPerformanceLogHeader(request);
        PerformanceLogger.append(LogBody.DERBY_HOTEL_CODES, getHotelCodes(request.getHotelAvailRQ()));
        PerformanceLogger.append(LogBody.CHECK_IN, request.getHotelAvailRQ().getAvailCriteria().getStayDateRange().getCheckin());
        PerformanceLogger.append(LogBody.CHECK_OUT, request.getHotelAvailRQ().getAvailCriteria().getStayDateRange().getCheckout());
        PerformanceLogger.append(LogBody.NUMBER_OF_UNIT, String.valueOf(request.getHotelAvailRQ().getAvailCriteria().getRoomStayCandidate().getNumberOfUnits()));
        PerformanceLogger.append(LogBody.ADULT, String.valueOf(request.getHotelAvailRQ().getAvailCriteria().getRoomStayCandidate().getAdultCount()));
        PerformanceLogger.append(LogBody.CHILD, String.valueOf(request.getHotelAvailRQ().getAvailCriteria().getRoomStayCandidate().getChildCount()));

        if (response.getError() != null) {
            TransactionLogger.getAvailLogger().get().setError(true);
            TransactionLogger.getAvailLogger().get().setException(response.getError().getMessage());
            PerformanceLogger.append(LogBody.PROCESSED_RESULT, LogBody.ProcessResult.Fail.name());
        } else {
            PerformanceLogger.append(LogBody.PROCESSED_RESULT, LogBody.ProcessResult.Success.name());
        }
        PerformanceLogger.save(executor);
    }

    private String getHotelCodes(HotelAvailRQ hotelAvailRQ) {
        StringBuilder builder = new StringBuilder();
        for (String s : hotelAvailRQ.getAvailCriteria().getHotelCodesList()) {
            builder.append(s).append(" ");
        }
        return builder.toString();
    }

    private void setPerformanceLogHeader(AbstractRQ request) {
        PerformanceLogger.append(LogBody.TASK_ID, request.getHeader().getTaskId());
        PerformanceLogger.append(LogBody.CHANNEL, request.getHeader().getSource());
        PerformanceLogger.append(LogBody.PROVIDER_CHAIN, "LowCostBeds");
    }

    @Value("${dswitch3.adapter}")
    protected String adapter;

    @Autowired
    @Qualifier("hotelAvailabilityHandler")
    private RemoteServiceHandler<HotelAvailRequest, HotelAvailResponse> hotelAvailHandler;

    @Autowired
    @Qualifier("hotelNoCachedAvailHandler")
    private RemoteServiceHandler<HotelAvailRequest, HotelAvailResponse> hotelNoCachedAvailHandler;

    @Autowired
    @Qualifier("hotelReservationHandler")
    private RemoteServiceHandler<HotelReservationRequest, HotelReservationResponse> hotelReservationHandler;

    @Autowired
    @Qualifier("hotelCancelHandler")
    private RemoteServiceHandler<HotelCancelRequest, HotelCancelResponse> hotelCancelHandler;

    @Autowired
    @Qualifier("storageAvailHandler")
    private RemoteServiceHandler<HotelAvailRequest, HotelAvailResponse> storageAvailHandler;

    @Autowired
    private TransactionLogRepository transactionLogRepository;

    @Autowired
    private ReservationLogRepository reservationLogRepository;

    @Autowired
    @Qualifier("taskExecutor")
    private Executor executor;

    @Autowired
    private SystemVariableCacheService systemVariableCacheService;

    @Autowired
    private SystemVariableRepository systemVariableRepository;
}
